﻿using System;
using System.Linq;
using System.Linq.Expressions;

namespace Cesave.Domain.Core.Specification
{
    /// <summary>
    /// Métodos de extensión para AND y OR re atando parámetros.
    /// </summary>
    public static class ExpressionBuilder
    {
        /// <summary>
        /// Compone dos expresiones y une todo en una nueva expresión.
        /// </summary>
        /// <typeparam name="T">Tipo de los parámetros en la expresión.</typeparam>
        /// <param name="first">Instancia Expression.</param>
        /// <param name="second">Expression a unir.</param>
        /// <param name="merge">Func a unir.</param>
        /// <returns>Expresiones unidas.</returns>
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // construye mapa de parámetros (de los parámetros de la segunda a los parámetros de la primera expresión)
            var map = first.Parameters.Select((f, i) => new
            {
                f,
                s = second.Parameters[i]
            }).ToDictionary(p => p.s, p => p.f);

            // reemplaza parámetros en la segunda expresión lambda con parámetros de la primera expresión
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            // aplica la composición de los cuerpos de la expresión lambda a los parámetros de la primera expresión.
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }
        /// <summary>
        /// Operador AND.
        /// </summary>
        /// <typeparam name="T">Tipo de paráemtros en la expresión.</typeparam>
        /// <param name="first">Expresión derecha en la operación AND.</param>
        /// <param name="second">Expresión izquierda en la operación AND.</param>
        /// <returns>Nueva expresión AND.</returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }
        /// <summary>
        /// Operador OR.
        /// </summary>
        /// <typeparam name="T">Tipo del parámetro en la expresión.</typeparam>
        /// <param name="first">Expresión derecha de la operación OR.</param>
        /// <param name="second">Expresión izquierda en la operación OR.</param>
        /// <returns>Expresiones OR nuevas.</returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }

    }
}
